/*
 * $AIST_Release: 0.9.0 $
 * Copyright 2011 Information Technology Research Institute, National
 * Institute of Advanced Industrial Science and Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package generator

import scala.collection.mutable.{Set => MSet}

import com.google.protobuf.Descriptors.Descriptor

import parser.TreeWalker
import parser.DataTypes._
import parser.Nodes._
import parser.Results._
import parser.analyzer.TableSerializationInfoMaker.mkKeyTupleType
import parser.analyzer.TableSerializationInfoMaker.mkRealValueTypeFromTuple
import parser.analyzer.TableSerializationInfoMaker.mkArrayTuple

object TypeMapper {

  /**
   * Makes TypeMapper.
   *
   * @param ast          Abstract syntax tree.
   * @param typeMap      Type reference map.
   * @param anonymousMap Map of data type and anonymous PB Messages.
   * @return TypeMapper.
   */
  def apply(ast: List[Stmt], typeMap: Map[Int, DataType], anonymousMap: Map[DataType, Descriptor]): TypeMapper = {
    val dataTypes = collectClasses(ast, typeMap)
    val c = Stream.from(0).iterator
  
    val table = dataTypes.flatMap { x =>
      x match {
        case MessageType(desc, _) => Some((x, desc.getName))
        case _: ArrayType => 
          val t = mkArrayTuple(x)
          anonymousMap.get(t).map { desc =>
             (t, desc.getName)
          }
        case _: TupleType => 
          anonymousMap.get(x) match {
            case Some(desc) => Some((x, desc.getName))
            case None       => Some((x, "type_%03d".format(c.next)))
          }
        case _ => error("Unexpect type %s".format(x))
      }
    }
    return new TypeMapper(Map() ++ table, typeMap)
  }

  /**
   * Collects data types to convert Java Class.
   */
  private def collectClasses(ast: List[Stmt], typeMap: Map[Int, DataType]): Set[DataType] = {
    val dataTypes = MSet[DataType]()

    def addType(dataType: DataType): Unit = {
      def addTupleMembers(t: TupleType): Unit = {
        t.members.foreach { case (_, dataType) =>
          dataType match {
            case ArrayType(contents) => addType(contents)
            case _ => addType(dataType)
          }
        }
      }

      dataType match {
        case _: BuildInDataType  => // Do nothing
        case ArrayType(contents) => 
          dataTypes += dataType
          addType(contents)

        case MapType(key, value) => 
          addType(key)
          addType(value)

        case t: TupleType =>
          dataTypes += t
          addTupleMembers(t)

        case x: TypeReference =>
          typeMap.get(x.id) match {
            case Some(x) => addType(x)
            case None    => error("Invalid type id %s".format(x))
          }

        case x: EnumType => // Treat "enum" as "Int"
        case x: FunctionType =>
        case _ => error("Invalid type")
      }
    }

    // Function Body

    TreeWalker.walkTree(classOf[Node], ast) {
      case s: TableInit =>
        val TableType(keyType, valType, opts) = s.dataType

        val realKeyType = mkKeyTupleType(keyType)
        val realValType = mkRealValueTypeFromTuple(valType, opts.toList)

        addType(realKeyType)
        addType(realValType)
        Success(Unit)

      case s: DefStaticVarStmt =>
        addType(s.value.dataType)
        Success(Unit)

      case s: VarInit =>
        addType(s.value.dataType)
        Success(Unit)
    }

    return Set() ++ dataTypes
  }
}

/**
 * Mapper from data type to Java code string.
 *
 * @param classMap Map from data type to Java Class name.
 * @param typeMap  Type reference map.
 */
class TypeMapper private(val classMap: Map[DataType, String], val typeMap: Map[Int, DataType]) {

  /**
   * Used class names.
   */
  def usedClasses(): Set[String] = Set() ++ classMap.values

  /**
   * Maps from data type to Java code string.
   */
  def map(dataType: DataType): String = {
    val prefix = "DataTypes."
    dataType match {
      case IntType                       => "Long"
      case FloatType                     => "Double"
      case TimeType                      => "Helpers.Time"
      case FingerPrintType               => "Helpers.FingerPrintType"
      case StringType                    => "String"
      case BytesType                     => "Helpers.ByteStringWrapper"
      case ArrayType(contents)           => "List<%s>".format(map(contents))
      case MapType(keys, values)         => "Map<%s, %s>".format(map(keys), map(values))
      case x: MessageType                => prefix + classMap(x)
      case x: TupleType                  => prefix + classMap(x)
      case x: EnumType                   => "Integer"
      case x: TypeReference              => typeMap.get(x.id) match {
        case Some(x) => map(x)
        case None => error("Invalid type id %d".format(x.id))
      }
      case x: FunctionType => 
        "%sSCFunction%03d<%s>".format(prefix, x.argsType.length, (x.retType::x.argsType).map(map).mkString(","))
      
      case _ => error("Invalid Type")
    }
  }
}
